#!/usr/bin/env python
#-*- coding:utf-8 -*-

import argparse, os, rdflib, re
from django.template import Context, Template
from django.conf import settings
from rdflib import plugin
from rdflib import ConjunctiveGraph as Graph

## Example use:
## $ ./Docgen.py -i ../gltype.ttl -t template.wiki -o ../../grey-literature-typology-wiki/Vocabulary_of_the_Types_of_Grey_Literature.wiki

"""
  Sample concept output:
  {
   "label" : "Abstract",
   "predicates" : [
      {
        "predicateLabel" : "Preferred label",
        "predicateUri" : "http://www.w3.org/2004/02/skos/core#prefLabel",
        "values" : [
          {
            "type" : "literal",
            "label" : "Abstrakt",
            "language" : "cs",
          },
          {
            "type" : "literal",
            "label" : "Abstract",
            "language" : "en",
          },
        ],
      },
      {
        "predicateLabel" : "Exact match",
        "predicateUri" : "http://www.w3.org/2004/02/skos/core#exactMatch",
        "values" : [
          {
            "type" : "uri",
            "label" : "dbpedia:Abstract_(summary)",
            "uri" : "http://dbpedia.org/resource/Abstract_(summary)",
          },
        ],
      },
      {
        "predicateLabel" : "Type",
        "predicateUri" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
        "values" : [
          {
            "type" : "uri",
            "label" : "skos:Concept",
            "uri" : "http://www.w3.org/2004/02/skos/core#Concept",
          },
        ]
      },
      {
        "predicateLabel" : "Broader term",
        "predicateUri" : "http://www.w3.org/2004/02/skos/core#broader",
        "values" : [
          {
            "type" : "uri",
            "label" : "Informative document",
            "uri" : "#Informative_document",
          },
        ],
      },
    ],
  }
"""
    
    
class Docgen (Graph):
  """
    Generate documentation from an RDF file of grey literature typology
  """
  
  def __init__(self): 
    self.parseArgs()
    Graph.__init__(self)
    # Boiler-plate code to register SPARQL processor
    plugin.register(
      "sparql", rdflib.query.Processor,
      "rdfextras.sparql.processor", "Processor")
    plugin.register(
      "sparql", rdflib.query.Result,
      "rdfextras.sparql.query", "SPARQLQueryResult") 
    self.namespaces = {
      "http://purl.org/ontology/bibo/" : "bibo",
      "http://dbpedia.org/resource/" : "dbpedia",
      "http://purl.org/dc/elements/1.1/" : "dc",
      "http://purl.org/dc/terms/" : "dcterms",
      "http://purl.org/dc/dcmitype/" : "dctype",
      "http://xmlns.com/foaf/0.1/" : "foaf",
      "http://purl.org/ntk/gltype#" : "gltype",
      "http://www.w3.org/2002/07/owl#" : "owl",
      "http://www.w3.org/1999/02/22-rdf-syntax-ns#" : "rdf",
      "http://www.w3.org/2000/01/rdf-schema#" : "rdfs",
      "http://www.w3.org/2004/02/skos/core#" : "skos",
      "http://www.w3.org/2001/XMLSchema#" : "xsd",
    }
    # Have a translation dict for Uris to labels
    self.translateToLabel = {
      "rdf:type" : "Type",
      "skos:altLabel" : "Alternative label",
      "skos:broadMatch" : "Broad match",
      "skos:definition" : "Definition",
      "skos:broader" : "Broader term",
      "skos:editorialNote" : "Editorial note",
      "skos:exactMatch" : "Exact match",
      "skos:closeMatch" : "Close match",
      "skos:example" : "Example",
      "skos:inScheme" : "In scheme",
      "skos:narrower" : "Narrower term",
      "skos:narrowMatch" : "Narrow match",
      "skos:prefLabel" : "Preferred label",
      "skos:topConceptOf" : "Top concept of",
    }
    
    self.parse(self.args.input, format="n3")
    self.getConceptUris()
  
  def parseArgs(self):
    parser = argparse.ArgumentParser(
      description = "Reference generator for Grey Literature Typology"
    )
    parser.add_argument(
      "-i",
      dest = "input",
      help = "RDF Turtle dump of the Grey Literatury Typology used a an input",
      required = True,
      type = argparse.FileType("r")
    )
    parser.add_argument(
      "-o",
      dest = "output",
      help = "File to output the generated vocabulary reference to",
      required = True,
      type = argparse.FileType("w")
    )
    parser.add_argument(
      "-t",
      dest = "template",
      help = "Template file in Google Code wiki markup",
      required = True,
      type = argparse.FileType("r")
    )
    self.args = parser.parse_args()
    
  def fixedQuery(self, querystring):
    processor = plugin.get("sparql", rdflib.query.Processor)(self)
    result = plugin.get("sparql", rdflib.query.Result)
    ns = dict(self.namespace_manager.namespaces())
    return result(processor.query(querystring, initNs = ns))

  def isGLType(self, reference, referenceType):
    if not referenceType in ["curie", "uri"]:
      raise Exception("Bad reference type {0}".format(referenceType))
    if referenceType == "curie":
      reference = self.stringifyCurie(reference)
    beginning = {
      "curie" : "gltype:",
      "uri" : "http://purl.org/ntk/gltype#",
    }[referenceType]
    if reference.startswith(beginning) and len(reference) > len(beginning):
      return True
    else:
      return False
      
  def uriToCurie(self, uri):
    status = False
    for namespace in self.namespaces:
      if uri.startswith(namespace):
        status = True
        return {
          "prefix" : self.namespaces[namespace],
          "localName" : uri[len(namespace):],
        }
    if not status:
      raise Exception("A prefix for URI {0} was not found.".format(uri))
  
  def curieToUri(self, curie):
    namespaceUri = False
    for namespace in self.namespaces.items():
      if curie["prefix"] == namespace[1]:
        namespaceUri = namespace[0]
    if namespaceUri:
      return namespaceUri + curie["localName"]
    else:
      raise Exception(
        "Namespace URI for curie {0} was not found.".format(
          self.stringifyCurie(curie)
        )
      )
  
  def stringifyCurie(self, curie):
    return "{0}:{1}".format(curie["prefix"], curie["localName"])
    
  def curieToLabel(self, curie):
    # Lookup label
    if self.isGLType(curie, referenceType = "curie"):
      return self.getPrefLabel(self.curieToUri(curie))
    else:
      # Stingify curie
      curie = self.stringifyCurie(curie)
      if self.translateToLabel.has_key(curie):
        return self.translateToLabel[curie]
      else:
        raise Exception("A label for curie {0} was not found.".format(curie))
     
  def uriToLabel(self, uri):
    # Lookup curie
    curie = self.uriToCurie(uri)
    # Lookup label
    label = self.curieToLabel(curie)
    return label
  
  def labelToHashUri(self, label):
    return "#{0}".format(label.replace(" ", "_"))
      
  def getConceptUris(self):
    self.conceptUris = []
    query = self.fixedQuery(
      """
        SELECT ?concept WHERE { 
          ?concept a skos:Concept . 
        }
        ORDER BY ?concept
      """
    )
    for conceptUri in query:
      self.conceptUris.append(str(conceptUri))
  
  def getPrefLabel(self, conceptUri, language = "en"):
    prefLabel = ""
    query = self.fixedQuery(
      """
        SELECT ?prefLabel WHERE {{
          <{0}> skos:prefLabel ?prefLabel .
        }}
      """.format(conceptUri)
    )
    for label in query:
      if label.language == language:
        prefLabel = unicode(label)
    if prefLabel:
      return prefLabel
    else:
      raise Exception("Preferred label for concept {0} was not found.".format(conceptUri))
  
  def getPredicates(self, conceptUri):
    predicates = []
    query = self.fixedQuery(
      """
        SELECT DISTINCT ?predicate WHERE {{
          <{0}> ?predicate ?o .
        }}
        ORDER BY ?predicate
      """.format(conceptUri)
    )
    for predicate in query:
      predicates.append(str(predicate))
    predicates = list(set(predicates)) # It seems SPARQL DISTINCT does not work in rdflib 3.0.0
    return predicates
  
  def getConcept(self, conceptUri):
    concept = {
      "label" : self.getPrefLabel(conceptUri),
      "predicates" : [],
      "uri" : conceptUri,
    }
    predicateUris = self.getPredicates(conceptUri)
    for predicateUri in predicateUris:
      concept["predicates"].append(
        self.getPredicateValues(conceptUri, predicateUri)
      )
    return concept
  
  def getPredicateValues(self, conceptUri, predicateUri):
    predicate = {
      "predicateLabel" : self.uriToLabel(predicateUri),
      "predicateUri" : predicateUri,
      "values" : [],
    }
    query = self.query(
      """
        SELECT ?o WHERE {{
          <{0}> <{1}> ?o .
        }}
      """.format(conceptUri, predicateUri)
    )
    for objectValue in query:
      # Template for output object
      outputObject = {
        "type" : "",
        "label" : "",
        "uri" : "",
      }
      # If object is a URI:
      if isinstance(objectValue, rdflib.URIRef):
        objectValue = unicode(objectValue)
        outputObject = {
          "type" : "uri",
        }
        if self.isGLType(objectValue, referenceType = "uri"):
          outputObject["label"] = self.getPrefLabel(objectValue)
          outputObject["uri"] = self.labelToHashUri(outputObject["label"])
        else:
          curie = self.stringifyCurie(self.uriToCurie(objectValue))
          if curie == "gltype:":
            outputObject["label"] = "Vocabulary of the Types of Grey Literature" 
            outputObject["uri"] = self.labelToHashUri(outputObject["label"])
          else:
            outputObject["label"] = curie
            outputObject["uri"] = objectValue
      # If object is a literal:
      elif isinstance(objectValue, rdflib.Literal): 
        outputObject = {
          "type" : "literal",
          "label" : unicode(objectValue),
          "language" : objectValue.language,
        }
      else:
        raise Exception("Unhandled type {0}.".format(type(objectValue)))
      predicate["values"].append(outputObject)
    return predicate
  
  def renderTemplate(self, inputData, template, output):
    templateString = template.read()
    template.close()
    template = Template(templateString)
    renderedTemplate = template.render(
      Context({"types" : inputData})
    )
    # Crufty solution to remove additional linebreaks
    renderedTemplate = re.sub("\n\s*\n", "\n", renderedTemplate)
    output.write(renderedTemplate.encode("utf8"))
    output.close()
      
  def main(self):
    settings.configure()
    output = []
    for conceptUri in self.conceptUris:
      concept = self.getConcept(conceptUri)
      output.append(concept)
      
    self.renderTemplate(
      output,
      self.args.template,
      self.args.output
    )

    
def main():
  if int(rdflib.__version__[:1]) > 2:
    d = Docgen()
    d.main()
    print "[INFO] Don't forget to commit and update the HG repository for the wiki!"
  else:
    raise ImportError("This script needs rdflib version 3.0.0 and higher!")
  
if __name__ == "__main__":
  main()
